{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "362520fb9721197",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 损失函数\n",
    "\n",
    "均方误差\n",
    "$$\n",
    "E=\\frac{1}{2} \\sum_k\\left(y_k-t_k\\right)^2\n",
    "$$\n",
    "\n",
    "交叉熵误差\n",
    "$$\n",
    "E=-\\sum_k t_k \\log y_k\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c472a36dd7acdb97",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T02:44:25.395628Z",
     "start_time": "2025-06-19T02:44:25.340370Z"
    },
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 均方误差\n",
    "import numpy as np\n",
    "def mean_squared_error(y,t):\n",
    "    return 0.5 * np.sum((y-t)**2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ed22e44b8c2721d5",
   "metadata": {
    "collapsed": false,
    "is_executing": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.09750000000000003\n",
      "0.5975\n"
     ]
    }
   ],
   "source": [
    "# 设\"2\"为正确解\n",
    "t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]\n",
    "\n",
    "# 例 1：“2”的概率最高的情况（0.6）\n",
    "y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]\n",
    "print(mean_squared_error(np.array(y), np.array(t)))\n",
    "\n",
    "\n",
    "# 例 2：“7”的概率最高的情况（ 0.6）\n",
    "y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]\n",
    "print(mean_squared_error(np.array(y), np.array(t)))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "229fa7c58cf64360",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 交叉熵误差\n",
    "# 交叉熵误差的值是由正确解标签所对应的输出结果决定的。\n",
    "def cross_entropy_error(y, t):\n",
    "    # 1e-7 是科学计数法，表示很小的数。\n",
    "    # e 在 Python 里不是科学计数法的“e”，而是数学常数。\n",
    "    # 所以要写 1e-7，不能只写 e。\n",
    "    delta = 1e-7\n",
    "    return -np.sum(t * np.log(y + delta))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "abf1493f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.510825457099338\n"
     ]
    }
   ],
   "source": [
    "t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]\n",
    "y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]\n",
    "print(cross_entropy_error(np.array(y), np.array(t)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3856eef3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.302584092994546\n"
     ]
    }
   ],
   "source": [
    "y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]\n",
    "print(cross_entropy_error(np.array(y), np.array(t)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "37d9c532",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-18T15:22:06.383286Z",
     "start_time": "2025-06-18T15:22:06.142996Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 开始加载数据 ---\n",
      "数据加载与预处理完成！\n",
      "训练集大小: 60000 条\n",
      "测试集大小: 10000 条\n",
      "--------------------\n"
     ]
    }
   ],
   "source": [
    "import gzip\n",
    "import os\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "def load_mnist_images(filename):\n",
    "    \"\"\"\n",
    "    加载MNIST的图片数据。\n",
    "    \"\"\"\n",
    "    file_path = os.path.join('mnist_data', filename)\n",
    "    with gzip.open(file_path, 'rb') as f:\n",
    "        # MNIST的图片数据文件的前16个字节是文件头信息，需要跳过\n",
    "        # 之后的数据才是真正的像素值\n",
    "        data = np.frombuffer(f.read(), np.uint8, offset=16)\n",
    "    # 将一维数组重塑为 (图片数量, 28, 28) 的三维数组\n",
    "    images = data.reshape(-1, 28, 28)\n",
    "    return images\n",
    "\n",
    "def load_mnist_labels(filename):\n",
    "    \"\"\"\n",
    "    加载MNIST的标签数据。\n",
    "    \"\"\"\n",
    "    file_path = os.path.join('mnist_data', filename)\n",
    "    with gzip.open(file_path, 'rb') as f:\n",
    "        # 标签文件的前8个字节是文件头信息\n",
    "        data = np.frombuffer(f.read(), np.uint8, offset=8)\n",
    "    return data\n",
    "\n",
    "\n",
    "def preprocess_data(images, labels):\n",
    "    \"\"\"\n",
    "    数据预处理：\n",
    "    1.  将28x28的图片“压平”成784维的向量。\n",
    "    2.  将像素值从[0, 255]归一化到[0.01, 1.0]。\n",
    "        (我们不缩放到[0,1]是为了避免0值输入导致权重更新出问题)\n",
    "    3.  将标签进行\"one-hot\"编码。例如，数字5会变成 [0,0,0,0,0,1,0,0,0,0]。\n",
    "    \"\"\"\n",
    "    # 压平图片并归一化\n",
    "    images_flattened = images.reshape(images.shape[0], -1).astype(np.float32)\n",
    "    images_normalized = (images_flattened / 255.0 * 0.99) + 0.01\n",
    "\n",
    "    # One-hot编码标签\n",
    "    # 创建一个全为0.01的矩阵，然后将对应标签位置的值设为0.99\n",
    "    labels_one_hot = np.zeros((labels.size, 10)) + 0.01\n",
    "    for i, label in enumerate(labels):\n",
    "        labels_one_hot[i][label] = 0.99\n",
    "\n",
    "    return images_normalized, labels_one_hot\n",
    "\n",
    "# 执行下载和加载\n",
    "print(\"--- 开始加载数据 ---\")\n",
    "# download_mnist()\n",
    "# 深度学习/mnist_data/t10k-images-idx3-ubyte.gz\n",
    "train_images = load_mnist_images('train-images-idx3-ubyte.gz')\n",
    "train_labels = load_mnist_labels('train-labels-idx1-ubyte.gz')\n",
    "test_images = load_mnist_images('t10k-images-idx3-ubyte.gz')\n",
    "test_labels = load_mnist_labels('t10k-labels-idx1-ubyte.gz')\n",
    "\n",
    "\n",
    "# 预处理数据\n",
    "X_train, y_train = preprocess_data(train_images, train_labels)\n",
    "X_test, y_test_one_hot = preprocess_data(test_images, test_labels)\n",
    "# 测试时，我们还是用原始的数字标签进行比较\n",
    "y_test = test_labels\n",
    "\n",
    "print(\"数据加载与预处理完成！\")\n",
    "print(f\"训练集大小: {X_train.shape[0]} 条\")\n",
    "print(f\"测试集大小: {X_test.shape[0]} 条\")\n",
    "print(\"-\" * 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "04db07ae",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-18T16:34:36.977052Z",
     "start_time": "2025-06-18T16:34:36.973642Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "60000\n",
      "[10210 11239 39053 29433 28393  5113 14864 41287 51448 29322]\n",
      "[[0.01 0.01 0.01 ... 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 ... 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 ... 0.01 0.01 0.01]\n",
      " ...\n",
      " [0.01 0.01 0.01 ... 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 ... 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 ... 0.01 0.01 0.01]]\n",
      "[[0.01 0.01 0.01 0.99 0.01 0.01 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.99 0.01]\n",
      " [0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.99 0.01 0.01]\n",
      " [0.01 0.01 0.01 0.01 0.01 0.99 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 0.01 0.99 0.01 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.99 0.01 0.01 0.01 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 0.99 0.01 0.01 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 0.99 0.01 0.01 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.99 0.01 0.01 0.01 0.01 0.01 0.01 0.01]\n",
      " [0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.99]]\n"
     ]
    }
   ],
   "source": [
    "train_size = X_train.shape[0]\n",
    "print(train_size)\n",
    "batch_size = 10\n",
    "batch_mask = np.random.choice(train_size, batch_size)\n",
    "print(batch_mask)\n",
    "x_batch = X_train[batch_mask]\n",
    "print(x_batch)\n",
    "t_batch = y_train[batch_mask]\n",
    "print(t_batch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "32922ff756ebed07",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T02:44:28.796497Z",
     "start_time": "2025-06-19T02:44:28.794329Z"
    },
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n"
     ]
    }
   ],
   "source": [
    "a = np.array([[2,2],[2,4]])\n",
    "print(a.ndim)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf501822baa8debd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T05:39:41.229245Z",
     "start_time": "2025-06-19T05:39:41.226959Z"
    },
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 普通的乘法，不是矩阵乘法 one-hot表示\n",
    "def cross_entropy_error(y,t):\n",
    "    if y.ndim == 1:\n",
    "        t = t.reshape(1,t.size)\n",
    "        y = y.reshape(1,y.size)\n",
    "    batch_size = y.shape[0]\n",
    "    return -np.sum(t * np.log(y+1e-7)) / batch_size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8bb89d3126d88107",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T16:09:19.349434Z",
     "start_time": "2025-06-19T16:09:19.345994Z"
    },
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 非one-hot表示\n",
    "# y是神经网络的输出，t是监督数据\n",
    "def cross_entropy_error(y,t):\n",
    "    if y.ndim == 1:\n",
    "        t = t.reshape(1,t.size)\n",
    "        y = y.reshape(1,y.size)\n",
    "    batch_size = y.shape[0]\n",
    "    if t.size == y.size:  # 如果t是one-hot编码\n",
    "        return -np.sum(t * np.log(y + 1e-7)) / batch_size\n",
    "    else:  # 如果t是数字标签\n",
    "        return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e3c24eb86701478b",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T16:11:16.806150Z",
     "start_time": "2025-06-19T16:11:15.713351Z"
    },
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 求导数\n",
    "import numpy as np\n",
    "import matplotlib.pylab as plt\n",
    "\n",
    "def function_1(x):\n",
    "    return 0.01*x**2 + 0.1*x\n",
    "\n",
    "x = np.arange(0.0, 20.0, 0.1) # 以 0.1为单位，从 0到 20的数组 x\n",
    "y = function_1(x)\n",
    "\n",
    "plt.xlabel(\"x\")\n",
    "plt.ylabel(\"f(x)\")\n",
    "plt.plot(x, y)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9dd398a043b9313f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T16:30:57.663688Z",
     "start_time": "2025-06-19T16:30:57.659579Z"
    },
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.0\n",
      "0.0\n",
      "0.1999999999990898\n",
      "0.2999999999986347\n"
     ]
    }
   ],
   "source": [
    "def numerical_diff1(f, x):\n",
    "    h = 10e-50\n",
    "    return (f(x+h) - f(x)) / h\n",
    "\n",
    "# 利用微小的差分求导数的过程称为数值微分 （numerical differentiation）\n",
    "def numerical_diff2(f, x):\n",
    "    h = 1e-4 # 0.0001\n",
    "    return (f(x+h) - f(x-h)) / (2*h)\n",
    "\n",
    "print(numerical_diff1(function_1,5))\n",
    "print(numerical_diff1(function_1,10))\n",
    "\n",
    "print(numerical_diff2(function_1,5))\n",
    "print(numerical_diff2(function_1,10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "5453e57984d595a0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T16:38:14.372874Z",
     "start_time": "2025-06-19T16:38:14.370387Z"
    },
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6.00000000000378\n",
      "7.999999999999119\n"
     ]
    }
   ],
   "source": [
    "# 求偏导数\n",
    "def numerical_diff(f, x):\n",
    "    h = 1e-4 # 0.0001\n",
    "    return (f(x+h) - f(x-h)) / (2*h)\n",
    "\n",
    "def function_tmp1(x0):\n",
    "    return x0*x0 + 4.0**2.0\n",
    "print(numerical_diff(function_tmp1, 3.0))\n",
    "\n",
    "def function_tmp2(x1): \n",
    "    return 3.0**2.0 + x1*x1\n",
    "print(numerical_diff(function_tmp2, 4.0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fb924beea2b946f1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-19T16:43:16.108386Z",
     "start_time": "2025-06-19T16:43:16.101890Z"
    },
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6. 8.]\n",
      "[0. 4.]\n",
      "[6. 0.]\n"
     ]
    }
   ],
   "source": [
    "# 梯度\n",
    "def numerical_gradient(f, x):\n",
    "    h = 1e-4 # 0.0001\n",
    "    grad = np.zeros_like(x) # 生成和 x形状相同的数组\n",
    "    for idx in range(x.size):\n",
    "        tmp_val = x[idx]\n",
    "        # f(x+h)的计算\n",
    "        x[idx] = tmp_val + h\n",
    "        fxh1 = f(x)\n",
    "        # f(x-h)的计算\n",
    "        x[idx] = tmp_val - h\n",
    "        fxh2 = f(x)\n",
    "        grad[idx] = (fxh1 - fxh2) / (2*h)\n",
    "        x[idx] = tmp_val # 还原值\n",
    "    return grad\n",
    "\n",
    "def function_2(x):\n",
    "    return x[0]**2 + x[1]**2\n",
    "\n",
    "print(numerical_gradient(function_2, np.array([3.0, 4.0])))\n",
    "print(numerical_gradient(function_2, np.array([0.0, 2.0])))\n",
    "print(numerical_gradient(function_2, np.array([3.0, 0.0])))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ba5647a4d6206b1",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 梯度下降\n",
    "# 参数 f是要进行最优化的函数，init_x是初始值，lr是学习率 learning\n",
    "# rate，step_num是梯度法的重复次数\n",
    "def gradient_descent(f,init_x,lr=0.01,step_num=100):\n",
    "    x = init_x.copy()\n",
    "    for i in range(step_num):\n",
    "        grad = numerical_gradient(f, x)  # 计算梯度\n",
    "        x -= lr * grad  # 更新参数\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9fffb83a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def function_2(x):\n",
    "    return x[0]**2 + x[1]**2\n",
    "\n",
    "init_x = np.array([-3.0,4.0])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
